package CryptoPrim;

import Utils.ByteTree;
import ArithmeticObjects.*;
import ArithUtils.Array;
import ArithUtils.BigNumber;

/**
 * A class which represents a PT Group (Mw)
 * 
 * @author Itamar Carmel, amirzell
 */
public class PlaintextGroup {
	
	private VerfGroup Gq; // Group of elements of Mw (Mw = Gq^w)
	private int width; // width of the group (w)

	/**
	 * Constructor for PT Group from given group of elements (Gq) and
	 * the width (w) of elements.
	 * 
	 * @param Gq
	 * @param width
	 */
	public PlaintextGroup(VerfGroup Gq, int width) {
		this.Gq = Gq;
		this.width = width;
	}

	/**
	 * Binary operation which returns the multiplication of two PT's.
	 * 
	 * @param pt1
	 * @param pt2
	 * @return pt1 * pt2
	 */
	public Plaintext multiply(Plaintext pt1, Plaintext pt2) {
		Plaintext ret = new Plaintext();
		for (int i = 0; i < this.width; i++){
			ret.add(Gq.mult(pt1.get(i),pt2.get(i)));
		}
		return ret;
	}

	/**
	 * A method which compares two plaintexts of this width and Gq elements.
	 * The compare method operate coordinate by coordinate.
	 * 
	 * @param pt1
	 * @param pt2
	 * @return false - PT's are not equal | PT's not the same width; true - otherwise
	 */
	public boolean plaintextIsEqual(Plaintext pt1, Plaintext pt2) {
		if (pt1.getWidth() != pt2.getWidth())
			return false;
		for (int i=0; i < this.width; i++){
			if(!Gq.ElementIsEqual(pt1.get(i),pt2.get(i)))	
				return false;
		}
		return true;
	}

	/**
	 * Binary operation which returns the exponentiation of given PT and BigNumber. 
	 * 
	 * @param pt
	 * @param e
	 * @return pt^e
	 */
	public Plaintext pow(Plaintext pt, BigNumber e) {
		Plaintext ret = new Plaintext();
		for (int i=0; i < this.width; i++){
			ret.add(Gq.pow(pt.get(i),e));
		}
		return ret;
	}

	/**
	 * Getter for the group Gq of this PT Group.
	 * 
	 * @return Gq
	 */
	public VerfGroup getGq() {
		return this.Gq;
	}

	/**
	 * Binary operation which returns the exponentiation of given PT and RM (randomizer). 
	 * 
	 * @param pt
	 * @param rm
	 * @return res_pt[i] = pt[i] * rm[i]
	 */
	public Plaintext pow(Plaintext pt, Randomizer rm) {
		Plaintext ret = new Plaintext();
		for (int i=0; i < this.width; i++){
			ret.add(Gq.pow(pt.get(i),rm.get(i).getValue()));
		}
		return ret;
	}
	
	/**
	 * Converts PT to it's BT rep.
	 * 
	 * @param pt
	 * @return BT(pt)
	 */
	public ByteTree plaintextToBT(Plaintext pt){
		if (this.width == 1){
			return Gq.toByteTree(pt.getArr().get(0));
		} else{
			return Gq.toByteTree(pt.getArr());
		}
	}

	/**
	 * Converts BT to it's PT rep.
	 * 
	 * @param bt
	 * @return PT(bt)
	 */
	public Plaintext BTToPlaintext(ByteTree bt){
		Array<VerfGroup.VElement> elmArr = new Array<VerfGroup.VElement>();
		if (this.width == 1){
			elmArr.add(Gq.getElement(bt));
		} else{
			elmArr = Gq.getArrayOfElementsFromBT(bt);
		}
		return new Plaintext(elmArr);
	}
	
	/**
	 * Converts array of PTs to it's BT rep.
	 * according to the algorithm defined in the document.
	 * 
	 * @param bt
	 * @return arrayPTs(bt)
	 */
	public Array<Plaintext> BTToPlaintextArray(ByteTree bt) {
		Array<Plaintext> ret = new Array<Plaintext>();
		if (width != 1){
			Array<VerfGroup.VElement> tmp;
			for(int i=0; i<bt.getTheNChild(0).getAmountOfDirectChildren(); i++){
				tmp = new Array<VerfGroup.VElement>();
				for (int j=0; j<bt.getAmountOfDirectChildren(); j++){
					tmp.add(Gq.getElement(bt.getTheNChild(j).getTheNChild(i)));
				}
				ret.add(new Plaintext(tmp));
			}
		} else{
			for(int i=0; i<bt.getAmountOfDirectChildren(); i++){
				ret.add(this.BTToPlaintext(bt.getTheNChild(i)));
			}
		}
		return ret;
	}
	
	/**
	 * Converts array of PTs to it's BT rep.
	 * according to the algorithm defined in the document.
	 * 
	 * @param ptArr
	 * @return
	 */
	public ByteTree plaintextArrayToBT(Array<Plaintext> ptArr) {
		ByteTree bt = ByteTree.generateEmptyNode();
		if (this.width != 1){
			ByteTree tmp;
			for(int i=0; i<this.width; i++){
				tmp = ByteTree.generateEmptyNode();
				for(Plaintext pt : ptArr){
					tmp.addChild(Gq.toByteTree(pt.get(i)));
				}
				bt.addChild(tmp);
			}
		} else{
			for(Plaintext pt : ptArr){
				bt.addChild(this.plaintextToBT(pt));
			}
		}
		return bt;
	}

}
